package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.item.vo.SpuDTO;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.dao.GoodsDao;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: Rose
 * @Date: 2021/4/3 11:04
 * @description ${description}
 * @version: 1.0.0
 */

@Slf4j
@Service
public class SearchService {

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsDao goodsDao;

    @Autowired
    private ElasticsearchTemplate template;


    public Goods buildGGoods(Spu spu) throws Exception {

        // 查询分类
        List<Category> categoryList = categoryClient.quryCategoryByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        List<String> namesList = categoryList.stream().map(Category::getName).collect(Collectors.toList());
        // 查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());

        // 搜索字段
        String all = spu.getName() + StringUtils.join(namesList, " ") + brand.getName();

        // 查询sku
        List<Sku> skuList = goodsClient.querySkuBySpuId(spu.getId());
        // 对 skuList 进行处理
        Set<Long> priceList = skuList.stream().map(Sku::getPrice).collect(Collectors.toSet());
        List<Map<String, Object>> skus = new ArrayList<>();
        for (Sku skue : skuList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skue.getId());
            map.put("title", skue.getTitle());
            map.put("price", skue.getPrice());
            map.put("image", StringUtils.substringBefore(skue.getImages(), ","));
            skus.add(map);
        }
        String skusStr = JsonUtils.tostring(skus);


        // 查询规格参数
        List<SpecParam> params = specificationClient.queryParamList(null, true, spu.getCid3());
        // 查询 商品详情
        SpuDetail spuDetail = goodsClient.queryDetailByid(spu.getId());
        // 规格参数, key是规格参数的名字, 值是规格参数的值
        Map<String, Object> specs = new HashMap<>(16);
        if (spu.getBrandId().equals("223")) {
            // 获取 o通用规格参数
            //JSON.parseObject()
            Map<String, String> genericSpec = JsonUtils.toMap(spuDetail.getGenericSpec(), String.class, String.class);
            log.info(" 商品BrandId = {} 的通用规格参数: {}", spuDetail.getSpuId(), spuDetail.getGenericSpec());
            // 获取特有的规格参数
            String specialSpec = spuDetail.getSpecialSpec();
            log.info(" 商品BrandId = {} 的特有规格参数: {}", spuDetail.getSpuId(), specialSpec);
            Map<String, List<String>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<String, List<String>>>() {
            });

            for (SpecParam param : params) {
                // 规格名称
                String key = param.getName();
                Object value = "";
                // 判断是否是通用规格
                if (param.getGeneric()) {
                    value = genericSpec.get(String.valueOf(param.getId()));
                    // 判断是否是数值类型
                    if (param.getNumeric()) {
                        // 处理成段
                        value = chooseSegment(value.toString(), param);
                    }
                } else {
                    value = specialSpecMap.get(String.valueOf(param.getId()));
                }
                specs.put(key, value);
            }
        }
        // 构建对象
        Goods goods = new Goods();
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreatTime(spu.getCreateTime());
        goods.setId(spu.getId());
        goods.setAll(all);         //  搜索字段,包含标题, 分类,品牌, 规格等
        goods.setPrice(priceList); //  所有sku 的价格集合
        goods.setSku(skusStr);     //  所有sku的集合的 json 格式
        goods.setSpecs(specs);     //  所有的可搜索的规格参数
        goods.setSubTitle(spu.getSubTitle());

        return goods;
    }


    private String chooseSegment(String value, SpecParam specParam) {
        double val = Double.parseDouble(value);

        String reslut = "其他";
        // 保存数值段
        for (String seqment : specParam.getSegments().split(",")) {
            String[] segs = seqment.split("-");
            // 获取数值范围
            double begin = Double.parseDouble(segs[0]);
            double end = Double.parseDouble(segs[1]);
            if (segs.length == 2) {
                end = Double.parseDouble(segs[1]);
            }
            // 判断是否在范围
            if (val > begin && val < end) {
                if (segs.length == 1) {
                    reslut = segs[0] + specParam.getUnit() + "以上";
                } else if (begin == 0) {
                    reslut = segs[1] + specParam.getUnit() + "以下";
                } else {
                    reslut = seqment + specParam.getUnit();
                }
                break;
            }
        }
        return reslut;
    }


    /**
     * @param request
     * @return
     */
    public PageResult<Goods> search(SearchRequest request) {

        Integer page = request.getPage() - 1;

        Integer size = request.getSize();

        // 创建查询构建器
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        // 结果过滤
        nativeSearchQueryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "sku"}, null));

        // 分页
        nativeSearchQueryBuilder.withPageable(PageRequest.of(page, size));
        // 搜索条件
        //
        QueryBuilder basicQuery = buildBasicQuery(request); // QueryBuilders.matchQuery("all", request.getKey());
        nativeSearchQueryBuilder.withQuery(basicQuery);

        // 聚合分类和品牌
        // 聚合分类
        String categoryAggName = "category_agg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        // 聚合品牌
        String brandAggName = "brand_agg";
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));


        // 查询
        // Page<Goods> goods = goodsDao.search(nativeSearchQueryBuilder.build());

        AggregatedPage<Goods> goods = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);

        // 解析结果
        // 解析分页结果
        long totalElements = goods.getTotalElements();
        int totalPages = goods.getTotalPages();
        List<Goods> content = goods.getContent();

        // 解析聚合结果
        Aggregations aggregations = goods.getAggregations();

        List<Category> categoryList = parseCategoryAgg(aggregations.get(categoryAggName));
        List<Brand> brandList = parseBrandAgg(aggregations.get(brandAggName));

        List<Map<String, Object>> specs = null;

        if (categoryList != null && categoryList.size() == 1) {
            // 商品分类存在并且数量为1, 可以聚合规格参数
            specs = buildSpecificationAgg(categoryList.get(0).getId(), basicQuery);
        }

        return new SearchResult(totalElements, Long.valueOf(totalPages), content, categoryList, brandList, specs);
    }

    private QueryBuilder buildBasicQuery(SearchRequest request) {

        // 创建布尔查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()));
        // 过滤条件
        Map<String, String> filter = request.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            if (!"cid3".equals(key) && !"brandId".equals(key)) {
                key = "specs." + key + ".keyword";
            }
            String value = entry.getValue();
            boolQueryBuilder.filter(QueryBuilders.termQuery(key, value));
        }

        return boolQueryBuilder;
    }


    /**
     * @param
     * @param basicQuery
     * @return
     */
    private List<Map<String, Object>> buildSpecificationAgg(Long cid, QueryBuilder basicQuery) {

        List<Map<String, Object>> specList = new ArrayList<>();

        // 1. 查询需要聚合的规格参数
        List<SpecParam> specParamList = specificationClient.queryParamList(null, true, cid);
        // 2. 聚合
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 2.1 带上查询条件
        nativeSearchQueryBuilder.withQuery(basicQuery);
        // 2.1 进行聚合
        for (SpecParam param : specParamList) {
            String name = param.getName();
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name + ".keyword"));
        }
        // 3. 获取结果
        AggregatedPage<Goods> goods = template.queryForPage(nativeSearchQueryBuilder.build(), Goods.class);
        // 4. 解析结果
        Aggregations aggregations = goods.getAggregations();

        for (SpecParam param : specParamList) {

            String name = param.getName();
            log.info("key = {}", name);
            Aggregation aggregation = aggregations.get(name);
            String type = aggregation.getType();
            Map<String, Object> metaData = aggregation.getMetaData();
            log.info("Aggregation name={}, type={}, metaData = {}", name, type, CollectionUtils.isEmpty(metaData) ? "为空" : JsonUtils.tostring(metaData));
            List<String> options = new ArrayList<>(); // aggregation.getBuckets().stream().map().collect(Collectors.toList());
            // 准备map
            Map<String, Object> map = new HashMap<>();
            map.put("k", name);
            map.put("options", options);

            specList.add(map);


        }

        return specList;
    }


    /**
     * @param aggregation
     * @return
     */
    private List<Brand> parseBrandAgg(LongTerms aggregation) {

        try {
            List<LongTerms.Bucket> buckets = aggregation.getBuckets();

            List<Long> longList = buckets.stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());

            List<Brand> brandList = brandClient.queryBrandByIds(longList);

            return brandList;
        } catch (Exception e) {
            log.info("[搜索服务]品牌列表查询出现异常:" + e.getMessage());
            return null;
        }
    }


    /**
     * @param aggregation
     * @return
     */
    private List<Category> parseCategoryAgg(LongTerms aggregation) {
        try {
            List<LongTerms.Bucket> buckets = aggregation.getBuckets();
            List<Long> longList = buckets.stream().map(b -> b.getKeyAsNumber().longValue())
                    .collect(Collectors.toList());

            List<Category> categoryList = this.categoryClient.quryCategoryByIds(longList);

            return categoryList;

        } catch (Exception e) {
            log.info("[搜索服务]查询商品种类出现异常: " + e.getMessage());
            return null;
        }
    }

    public void createOrUpdateIndex(Long spuId) throws Exception {

//        try {
        // 查询spu
        SpuDTO spuDTO = goodsClient.querySpuById(spuId);
        Spu spu = new Spu();
        BeanUtils.copyProperties(spuDTO, spu);
        // 构建 goods
        Goods goods = buildGGoods(spu);
        // 存入索引库
        this.goodsDao.save(goods);

//        }catch (Exception e ){
//            log.info("接收top消息，构建对象出现异常： " + e.getMessage());
//        }
    }


    public void deleteIndex(Long spuId) {

        this.goodsDao.deleteById(spuId);
    }
}
